import shutil
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import requests
import os
import threading
import time
import transmissionrpc
import configparser
import subprocess
import importlib.util
import glob
from PIL import Image, ImageTk
from io import BytesIO
# 创建加载窗口
loader_window = tk.Tk()
loader_window.title("正在加载...")
loader_window.geometry("300x100")
loader_window.resizable(0, 0)
loader_text = tk.Label(loader_window, text="正在加载...")
loader_text.pack(pady=20)
loader_window.update()

if not os.path.exists("LDownload"):
    os.mkdir("LDownload")
if not os.path.exists("plugins"):
    os.mkdir("plugins")
if not os.path.exists("lib"):
    os.mkdir("lib")
bg_image = None
# 读取配置文件
config = configparser.ConfigParser()
config.read('config.ini')
if not config.has_section('settings'):
    config.add_section('settings')
    config.set('settings', 'default_save_dir', 'LDownload/')
    config.set('settings', 'default_num_parts', '32')
    with open('config.ini', 'w') as f:
        config.write(f)
if not config.has_section('btdownload'):
    config.add_section('btdownload')
    config.set('btdownload', 'host', 'localhost')
    config.set('btdownload', 'port', '9091')
    with open('config.ini', 'w') as f:
        config.write(f)

# 读取默认设置
default_save_dir = config.get('settings', 'default_save_dir')
default_num_parts = config.getint('settings', 'default_num_parts')
print(default_save_dir, " ", default_num_parts)

# 新增变量来跟踪活动线程数和BT功能可用性
active_threads = 0
bt_enabled = False  # 默认BT功能不可用

# Transmission RPC连接设置
transmission_host = config.get('btdownload', 'host')
transmission_port = config.getint('btdownload', 'port')

def connect_to_bt():
    global transmission_client
    try:
        # 连接Transmission
        transmission_client = transmissionrpc.Client(transmission_host, transmission_port)
        # 测试连接
        transmission_client.get_torrents()  # 尝试获取种子信息
    except Exception as e:
        global bt_enabled
        bt_enabled = False  # 设置BT功能不可用
        print("BT功能不可用:", e)
        messagebox.showerror("错误", "无法连接到Transmission服务，请检查配置。Python Output:" + str(e))

def update_active_threads_label():
    threads_label['text'] = f"当前活动线程数: {active_threads}"

def add_file(url):
    global filelist
    filelist.append(url)
    print(filelist)

def load_plugins(plugin_dir):
    descriptions = []
    loaded_plugins = set()

    plugin_paths = glob.glob(os.path.join(plugin_dir, '*.py')) + glob.glob(os.path.join(plugin_dir, '*.pyd'))
    
    for plugin_path in plugin_paths:
        plugin_name = os.path.basename(plugin_path)
        if plugin_name in loaded_plugins:
            print(f"插件 {plugin_name} 已加载，跳过。")
            continue

        try:
            if plugin_path.endswith('.py'):
                spec = importlib.util.spec_from_file_location("plugin", plugin_path)
                plugin = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(plugin)
            else:
                plugin = importlib.import_module(plugin_name[:-4])

            if hasattr(plugin, 'run'):
                print(f"执行插件: {plugin_path}")

                plugin_params = plugin.need()
                param_list = eval(f"[{plugin_params}]")

                run_thread = threading.Thread(target=plugin.run, args=param_list)
                run_thread.start()

                if hasattr(plugin, 'description'):
                    desc = plugin.description()
                else:
                    desc = plugin_name

                if hasattr(plugin, 'inject_class'):
                    with open('plugins/ftlib.py', encoding='utf-8') as file:
                        exec(file.read())  # 动态加载插件的类，并注入到mainwindow中，可以当作让插件自己提供API使用，方便扩展

                descriptions.append((plugin_name, desc, None))  # 没有错误信息
                loaded_plugins.add(plugin_name)
            else:
                print(f"插件 {plugin_path} 未定义 run 函数，标记为加载失败。")
                descriptions.append((plugin_name, "插件未定义 run 函数", "插件加载失败"))  # 添加错误信息

        except Exception as e:
            print(f"加载插件 {plugin_path} 时出错: {e}")
            descriptions.append((plugin_name, "插件加载失败", str(e)))  # 记录错误

    return descriptions

class PluginsAPI:
    def __init__(self, plugin_dir):
        self.plugin_dir = plugin_dir
        self.descriptions = load_plugins(plugin_dir)
        self.plugin_windows = []  # 存储所有插件窗口

    def add_file(self, url):
        global filelist
        filelist.append(url)
        print(filelist)

    def gets_filelist(self):
        for i in filelist:
            print(i + " ")

    def run_download(self, plugin_name):
        print("插件" + plugin_name + "开始调用PluginsAPI以运行下载任务")
        go()

    def plugin_window_create(self, title, geometry):
        # 创建插件的窗口，每个插件一个窗口
        new_window = tk.Toplevel(mainwindow)
        new_window.title(title)
        new_window.geometry(geometry)
        self.plugin_windows.append(new_window)  # 将新窗口添加到列表中
    
    def window_add_label(self, text, plugin_name):
        # 在插件窗口中添加标签
        if self.plugin_windows:
            label = tk.Label(self.plugin_windows[-1], text=text)
            label.pack(pady=10)

    def window_add_entry(self, text, plugin_name):
        # 在插件窗口中添加输入框
        if self.plugin_windows:
            entry = tk.Entry(self.plugin_windows[-1])
            entry.pack(pady=10)

    def window_add_button(self, text, plugin_name, command):
        # 在插件窗口中添加按钮
        if self.plugin_windows:
            button = tk.Button(self.plugin_windows[-1], text=text, command=command)
            button.pack(pady=10)

    def read_add_entry(self, plugin_name):
        # 读取插件窗口中的输入框内容
        if self.plugin_windows:
            return self.plugin_windows[-1].entry.get()

    def window_destroy(self, plugin_name):
        # 销毁插件窗口
        if self.plugin_windows:
            self.plugin_windows[-1].destroy()  # 销毁最近创建的窗口
            self.plugin_windows.pop()  # 移除窗口对象

    def close_all_windows(self):
        # 关闭所有插件窗口
        for window in self.plugin_windows:
            window.destroy()
        self.plugin_windows = []


        canvas.create_image(0, 0, image=bg_image, anchor='nw')
def download_file_with_progress(url, save_path, progress_bar, eta_label, speed_label):
    global active_threads
    active_threads += 1
    update_active_threads_label()

    total_size = 0
    downloaded_size = 0
    start_time = time.time()

    try:
        response = requests.head(url)
        response.raise_for_status()

        total_size = int(response.headers.get('content-length', 0))

        # 如果无法获取total_size，采用完整下载方式
        if total_size == 0:
            print("无法获取文件大小，采用全量下载")
            response = requests.get(url, stream=True)
            response.raise_for_status()

            with open(save_path, "wb") as f:
                for chunk in response.iter_content(chunk_size=8192):  # 增加chunk_size
                    if chunk:
                        f.write(chunk)
                        downloaded_size += len(chunk)
                        progress_bar['value'] = (downloaded_size / (downloaded_size if downloaded_size > 0 else 1)) * 100
                        eta_label['text'] = "剩余时间: 0秒"
                        speed_label['text'] = "速度: 0 MB/s"

            print(f"下载完成: {save_path}")
            messagebox.showinfo("下载完成", f"文件已成功下载并保存到: {save_path}")
            progress_bar['value'] = 100
            return

        # 分片下载
        part_size = max(total_size // (default_num_parts), 1)

        # 创建文件并占满所需的大小
        with open(save_path, "wb") as f:
            f.write(b'\0' * total_size)  # 先写入零填充文件

        download_parts = [0] * default_num_parts

        def download_part(part_num):
            nonlocal downloaded_size  
            start = part_num * part_size
            end = start + part_size - 1 if part_num < default_num_parts - 1 else ''
            headers = {'Range': f'bytes={start}-{end}'}
            try:
                part_response = requests.get(url, headers=headers, stream=True)
                part_response.raise_for_status()

                with open(save_path, "r+b") as f:  # 以读写方式打开文件
                    f.seek(start)  # 定位到指定的分片起始位置
                    for chunk in part_response.iter_content(chunk_size=8192):  # 增加chunk_size
                        if chunk:
                            f.write(chunk)  # 将下载的数据写入到文件
                            downloaded_size += len(chunk)
                            download_parts[part_num] += len(chunk)  
                            total_downloaded = sum(download_parts)
                            progress_bar['value'] = (total_downloaded / total_size) * 100 if total_size > 0 else 0

                            current_time = time.time()
                            elapsed_time = current_time - start_time
                            if elapsed_time > 0:
                                speed = total_downloaded / elapsed_time
                                if total_downloaded < total_size:
                                    remaining_time = (total_size - total_downloaded) / speed if speed > 0 else float('inf')
                                else:
                                    remaining_time = 0  
                                eta_label['text'] = f"剩余时间: {max(remaining_time, 0):.2f}秒"
                                speed_label['text'] = f"速度: {speed / 1024 / 1024:.2f} MB/s"

            except requests.exceptions.RequestException as e:
                print(f"下载部分失败: {e}")

        download_threads = []
        for i in range(default_num_parts):
            thread = threading.Thread(target=download_part, args=(i,))
            thread.start()
            download_threads.append(thread)

        for thread in download_threads:
            thread.join()
        
        print(f"下载完成: {save_path}")
        messagebox.showinfo("下载完成", f"文件已成功下载并保存到: {save_path}")

    except requests.exceptions.RequestException as e:
        print("下载过程中发生错误:", e)
    finally:
        active_threads -= 1
        update_active_threads_label()
        progress_bar['value'] = 0
        eta_label['text'] = "剩余时间: 0秒"
        speed_label['text'] = "速度: 0 MB/s"
        time.sleep(1)  # 1秒后清除进度条
        progress_bar.destroy()
        eta_label.destroy()
        speed_label.destroy()
        filelist = []

def go(callback=None):
    global save_dir
    if not os.path.exists(save_dir):
        os.makedirs(save_dir)

    def download_task():
        threads = []
        for url in filelist:
            file_name = os.path.join(save_dir, os.path.basename(url))
            progress_bar = ttk.Progressbar(download_tab, orient='horizontal', length=300, mode='determinate')
            progress_bar.pack(pady=10)
            eta_label = tk.Label(download_tab, text="剩余时间: 0秒")
            eta_label.pack(pady=5)
            speed_label = tk.Label(download_tab, text="速度: 0 MB/s")
            speed_label.pack(pady=5)

            thread = threading.Thread(target=download_file_with_progress, args=(url, file_name, progress_bar, eta_label, speed_label))
            thread.start()
            threads.append(thread)

        for thread in threads:
            thread.join()

        if callback:
            callback()

    threading.Thread(target=download_task).start()

def save_settings():
    global default_save_dir, default_num_parts, config
    default_save_dir = save_dir_entry.get()
    try:
        default_num_parts = int(num_parts_entry.get())
        if default_num_parts <= 0:
            raise ValueError("分片数必须大于0")
    except ValueError:
        messagebox.showerror("输入错误", "分片数必须是大于0的整数")
        return
    messagebox.showinfo("设置保存", "设置已成功保存。")
    config.set('settings', 'default_save_dir', default_save_dir)
    config.set('settings', 'default_num_parts', str(default_num_parts))
    with open('config.ini', 'w') as f:
        config.write(f)

filelist = []
save_dir = default_save_dir

def one_click_deploy_transmission():
    global filelist
    filelist = ["http://files.zhiyuhub.top/transmission-4.0.6-x64.msi"]
    save_dir = "LDownload/"

    messagebox.showinfo("提示", "开始下载 Transmission 安装包...")
    go(callback=install_transmission)

def install_transmission():
    print("开始安装 Transmission")
    messagebox.showinfo("提示", "正在安装，请耐心等待...")
    msi_file = os.path.join(save_dir, "transmission-4.0.6-x64.msi")
    if os.path.exists(msi_file):
        try:
            subprocess.run(['msiexec', '/i', msi_file, '/passive', '/norestart'], check=True)
            messagebox.showinfo("安装完成", "Transmission 已成功安装。")
        except subprocess.CalledProcessError as e:
            messagebox.showerror("安装错误", f"安装过程中发生错误: {e}")

def add_plugins():
    messagebox.showwarning("警告","来自未知来源的插件可能会导致您的计算机受到病毒、恶意软件、间谍软件的攻击，请在下载插件前先确认其来源的可靠性或仔细阅读插件代码。")

    #选择插件文件
    file_path = tk.filedialog.askopenfilename(title="选择插件文件", filetypes=[("Python文件", "*.py"), ("Python动态链接库", "*.pyd"), ("所有文件", "*")])
    #复制文件到plugins目录
    if file_path:
        file_name = os.path.basename(file_path)
        if os.path.exists(os.path.join("plugins", file_name)):
            messagebox.showerror("错误", "插件已存在，请勿重复添加。")
        else:
            try:
                shutil.copy(file_path, "plugins")
                messagebox.showinfo("提示", "插件添加成功。")
            except Exception as e:
                messagebox.showerror("错误", f"插件添加失败: {e}")
    else:
        messagebox.showinfo("提示", "未选择插件文件。")

# 创建主窗口
mainwindow = tk.Tk()
mainwindow.geometry("800x600")
mainwindow.title("LDownload 下载器")
# 设置缩放比例，防止在高缩放率下窗口大小显示不正常
mainwindow.tk.call('tk', 'scaling', 1.5)

# 连接到BT
# 创建选项卡
tabs = ttk.Notebook(mainwindow)

# 创建下载标签页
download_tab = ttk.Frame(tabs)
tabs.add(download_tab, text="下载")

# 创建插件标签页
plugin_tab = ttk.Frame(tabs)
tabs.add(plugin_tab, text="插件")

# 创建设置标签页
settings_tab = ttk.Frame(tabs)
tabs.add(settings_tab, text="设置")

about_tab = ttk.Frame(tabs)
tabs.add(about_tab, text="关于")

# market_tab = ttk.Frame(tabs)
# tabs.add(market_tab, text="插件市场")

tabs.pack(expand=1, fill="both")

# 下载标签页内容
download_entry = tk.Entry(download_tab, width=50)
download_entry.pack(pady=10)
add_button = tk.Button(download_tab, text="添加", command=lambda: add_file(download_entry.get()))
add_button.pack()
download_button = tk.Button(download_tab, text="下载", command=go)
download_button.pack(pady=10)

# 插件标签页内容
plugin_descriptions = load_plugins('plugins')
if not plugin_descriptions:
    tishi_label = tk.Label(plugin_tab, text="空空如也，没有插件。")
    tishi_label.pack(pady=10)
    plugin_doc_button = tk.Button(plugin_tab, text="插件文档", command=lambda: os.startfile("https://github.com/zhiyucn/zhiyu_app_docs/blob/main/LDownload/plugins/init_a_plugins.md"))
    plugin_doc_button.pack(pady=10)
    add_plugins_button = tk.Button(plugin_tab, text="添加插件", command=lambda: add_plugins())
    add_plugins_button.pack(pady=10)
else:
    tishi_label = tk.Label(plugin_tab, text="以下是插件列表:")
    tishi_label.pack(pady=10)
    plugin_doc_button = tk.Button(plugin_tab, text="插件文档", command=lambda: os.startfile("https://github.com/zhiyucn/zhiyu_app_docs/blob/main/LDownload/plugins/init_a_plugins.md"))
    plugin_doc_button.pack(pady=10)
    add_plugins_button = tk.Button(plugin_tab, text="添加插件", command=lambda: add_plugins())
    add_plugins_button.pack(pady=10)

plugin_label = tk.Label(plugin_tab, text="您当前安装了"+str(len(plugin_descriptions))+"个插件:")
plugin_label.pack(pady=10)

# 创建布局展示插件名字和描述
for name, desc, error in plugin_descriptions:
    frame = tk.Frame(plugin_tab)
    frame.pack(anchor='w', pady=(5, 0))

    name_label = tk.Label(frame, text=name, font=('Arial', 10, 'bold'))
    name_label.pack(side=tk.LEFT, pady=(5, 0))

    desc_label = tk.Label(frame, text=desc)
    desc_label.pack(side=tk.LEFT, padx=(10, 0))

    # 如果有错误信息，添加错误按钮在右侧
    if error:
        error_button = tk.Button(frame, text="查看错误", command=lambda e=error: messagebox.showinfo("插件加载错误", e))
        error_button.pack(side=tk.RIGHT, padx=(10, 0))

# 关于标签页内容
about_text = tk.Label(about_tab, text="LDownload 下载器\n作者: zhiyu\n版本: 1.2.3 Beta\n\n使用 Python 编写\n\n简洁，轻量的下载器\n\n支持 BT 下载\n\n支持插件扩展\n\n开源在Gitee上: https://gitee.com/zhiyuxl/LDownload")
about_text.pack(pady=10)
banquan_text = tk.Label(about_tab, text="Download icon by icons8")
banquan_text.pack(pady=5)

# 新增Label来显示活动线程数
threads_label = tk.Label(download_tab, text="当前活动线程数: 0")
threads_label.pack(pady=10)

# 设置标签页内容
save_dir_label = tk.Label(settings_tab, text="保存位置:")
save_dir_label.pack(pady=5)
save_dir_entry = tk.Entry(settings_tab, width=40)
save_dir_entry.insert(0, default_save_dir)
save_dir_entry.pack(pady=5)

num_parts_label = tk.Label(settings_tab, text="分片数:")
num_parts_label.pack(pady=5)
num_parts_entry = tk.Entry(settings_tab, width=10)
num_parts_entry.insert(0, str(default_num_parts))
num_parts_entry.pack(pady=5)

bt_enabled_label = tk.Label(settings_tab, text="BT功能:")
bt_enabled_label.pack(pady=5)
bt_host = tk.Text(settings_tab, width=30, height=1)
bt_host.insert(tk.END, transmission_host)
bt_host.pack(pady=5)
bt_port = tk.Text(settings_tab, width=10, height=1)
bt_port.insert(tk.END, str(transmission_port))
bt_port.pack(pady=5)
bt_test_button = tk.Button(settings_tab, text="连接到 Transmission", command=lambda: connect_to_bt())
bt_test_button.pack(pady=5)

# 一键部署按钮
deploy_bt_app = tk.Button(settings_tab, text="一键部署 Transmission", command=lambda: threading.Thread(target=one_click_deploy_transmission).start())
deploy_bt_app.pack(pady=5)

save_button = tk.Button(settings_tab, text="保存设置", command=save_settings)
save_button.pack(pady=10)

loader_window.destroy()
canvas = tk.Canvas(mainwindow, width=mainwindow.winfo_width(), height=mainwindow.winfo_height())
canvas.pack(fill="both", expand=True)

mainwindow.mainloop()
